/*
 ------------------------------------------------------------------------------
 Copyright (C) 2009-2011 Eternal Games.

 This file is part of the Eternal Tech source code.

 The Eternal Tech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Eternal Tech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Eternal Tech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// r_frontEnd.cpp - Handles view rendering
//

// TODO:
// - Subviews


#include "r_local.h"


/*
 ==============================================================================

    COORDINATE SCALING AND ASPECT RATIO CORRECTION

 ==============================================================================
*/


/*
 ==================
 R_AdjustHorzCoords
 ==================
*/
void R_AdjustHorzCoords (horzAdjust_t adjust, float percent, float xIn, float wIn, float *xOut, float *wOut)
{
	renderCrop_t   *renderCrop = &rg.renderCrops[rg.numRenderCrops-1];

	if (adjust == H_NONE)
	{
		*xOut = xIn * r_screenFraction->floatValue;
		*wOut = wIn * r_screenFraction->floatValue;

		return;
	}

	if (renderCrop->aspectRatio != ASPECT_WIDE)
	{
		*xOut = xIn * renderCrop->xScale;
		*wOut = wIn * renderCrop->xScale;

		return;
	}

	switch (adjust){
	case H_SCALE:
		*xOut = xIn * renderCrop->xScale;
		*wOut = wIn * renderCrop->xScale;

		break;
	case H_ALIGN_LEFT:
		*xOut = xIn * renderCrop->aspectScale + renderCrop->aspectBias * (1.0f - percent);
		*wOut = wIn * renderCrop->aspectScale;

		break;
	case H_ALIGN_CENTER:
		*xOut = xIn * renderCrop->aspectScale + renderCrop->aspectBias;
		*wOut = wIn * renderCrop->aspectScale;

		break;
	case H_ALIGN_RIGHT:
		*xOut = xIn * renderCrop->aspectScale + renderCrop->aspectBias * (1.0f + percent);
		*wOut = wIn * renderCrop->aspectScale;

		break;
	case H_STRETCH_LEFT:
		*xOut = xIn * renderCrop->aspectScale + renderCrop->aspectBias * (1.0f - percent);
		*wOut = wIn * renderCrop->aspectScale + renderCrop->aspectBias * percent;

		break;
	case H_STRETCH_WIDTH:
		*xOut = xIn * renderCrop->aspectScale + renderCrop->aspectBias * (1.0f - percent);
		*wOut = wIn * renderCrop->aspectScale + renderCrop->aspectBias * percent * 2.0f;

		break;
	case H_STRETCH_RIGHT:
		*xOut = xIn * renderCrop->aspectScale + renderCrop->aspectBias;
		*wOut = wIn * renderCrop->aspectScale + renderCrop->aspectBias * percent;

		break;
	}
}

/*
 ==================
 R_AdjustVertCoords
 ==================
*/
void R_AdjustVertCoords (vertAdjust_t adjust, float percent, float yIn, float hIn, float *yOut, float *hOut)
{
	renderCrop_t   *renderCrop = &rg.renderCrops[rg.numRenderCrops-1];

	if (adjust == V_NONE)
	{
		*yOut = yIn * r_screenFraction->floatValue;
		*hOut = hIn * r_screenFraction->floatValue;

		return;
	}

	if (renderCrop->aspectRatio != ASPECT_HIGH)
	{
		*yOut = yIn * renderCrop->yScale;
		*hOut = hIn * renderCrop->yScale;

		return;
	}

	switch (adjust){
	case V_SCALE:
		*yOut = yIn * renderCrop->yScale;
		*hOut = hIn * renderCrop->yScale;

		break;
	case V_ALIGN_TOP:
		*yOut = yIn * renderCrop->aspectScale + renderCrop->aspectBias * (1.0f - percent);
		*hOut = hIn * renderCrop->aspectScale;

		break;
	case V_ALIGN_CENTER:
		*yOut = yIn * renderCrop->aspectScale + renderCrop->aspectBias;
		*hOut = hIn * renderCrop->aspectScale;

		break;
	case V_ALIGN_BOTTOM:
		*yOut = yIn * renderCrop->aspectScale + renderCrop->aspectBias * (1.0f + percent);
		*hOut = hIn * renderCrop->aspectScale;

		break;
	case V_STRETCH_TOP:
		*yOut = yIn * renderCrop->aspectScale + renderCrop->aspectBias * (1.0f - percent);
		*hOut = hIn * renderCrop->aspectScale + renderCrop->aspectBias * percent;

		break;
	case V_STRETCH_HEIGHT:
		*yOut = yIn * renderCrop->aspectScale + renderCrop->aspectBias * (1.0f - percent);
		*hOut = hIn * renderCrop->aspectScale + renderCrop->aspectBias * percent * 2.0f;

		break;
	case V_STRETCH_BOTTOM:
		*yOut = yIn * renderCrop->aspectScale + renderCrop->aspectBias;
		*hOut = hIn * renderCrop->aspectScale + renderCrop->aspectBias * percent;

		break;
	}
}

/*
 ==================
 R_AdjustHorzCoordsInt
 ==================
*/
void R_AdjustHorzCoordsInt (horzAdjust_t adjust, float percent, int xIn, int wIn, int *xOut, int *wOut)
{
	renderCrop_t   *renderCrop = &rg.renderCrops[rg.numRenderCrops-1];

	if (adjust == H_NONE)
	{
		*xOut = mathMain::FloatToInt(xIn * r_screenFraction->floatValue) & ~1;
		*wOut = mathMain::FloatToInt(wIn * r_screenFraction->floatValue) & ~1;

		return;
	}

	if (renderCrop->aspectRatio != ASPECT_WIDE)
	{
		*xOut = mathMain::FloatToInt(xIn * renderCrop->xScale) & ~1;
		*wOut = mathMain::FloatToInt(wIn * renderCrop->xScale) & ~1;

		return;
	}

	switch (adjust){
	case H_SCALE:
		*xOut = mathMain::FloatToInt(xIn * renderCrop->xScale) & ~1;
		*wOut = mathMain::FloatToInt(wIn * renderCrop->xScale) & ~1;

		break;
	case H_ALIGN_LEFT:
		*xOut = mathMain::FloatToInt(xIn * renderCrop->aspectScale + renderCrop->aspectBias * (1.0f - percent)) & ~1;
		*wOut = mathMain::FloatToInt(wIn * renderCrop->aspectScale) & ~1;

		break;
	case H_ALIGN_CENTER:
		*xOut = mathMain::FloatToInt(xIn * renderCrop->aspectScale + renderCrop->aspectBias) & ~1;
		*wOut = mathMain::FloatToInt(wIn * renderCrop->aspectScale) & ~1;

		break;
	case H_ALIGN_RIGHT:
		*xOut = mathMain::FloatToInt(xIn * renderCrop->aspectScale + renderCrop->aspectBias * (1.0f + percent)) & ~1;
		*wOut = mathMain::FloatToInt(wIn * renderCrop->aspectScale) & ~1;

		break;
	case H_STRETCH_LEFT:
		*xOut = mathMain::FloatToInt(xIn * renderCrop->aspectScale + renderCrop->aspectBias * (1.0f - percent)) & ~1;
		*wOut = mathMain::FloatToInt(wIn * renderCrop->aspectScale + renderCrop->aspectBias * percent) & ~1;

		break;
	case H_STRETCH_WIDTH:
		*xOut = mathMain::FloatToInt(xIn * renderCrop->aspectScale + renderCrop->aspectBias * (1.0f - percent)) & ~1;
		*wOut = mathMain::FloatToInt(wIn * renderCrop->aspectScale + renderCrop->aspectBias * percent * 2.0f) & ~1;

		break;
	case H_STRETCH_RIGHT:
		*xOut = mathMain::FloatToInt(xIn * renderCrop->aspectScale + renderCrop->aspectBias) & ~1;
		*wOut = mathMain::FloatToInt(wIn * renderCrop->aspectScale + renderCrop->aspectBias * percent) & ~1;

		break;
	}
}

/*
 ==================
 R_AdjustVertCoordsInt
 ==================
*/
void R_AdjustVertCoordsInt (vertAdjust_t adjust, float percent, int yIn, int hIn, int *yOut, int *hOut)
{
	renderCrop_t   *renderCrop = &rg.renderCrops[rg.numRenderCrops-1];

	if (adjust == V_NONE)
	{
		*yOut = mathMain::FloatToInt(yIn * r_screenFraction->floatValue) & ~1;
		*hOut = mathMain::FloatToInt(hIn * r_screenFraction->floatValue) & ~1;

		return;
	}

	if (renderCrop->aspectRatio != ASPECT_HIGH)
	{
		*yOut = mathMain::FloatToInt(yIn * renderCrop->yScale) & ~1;
		*hOut = mathMain::FloatToInt(hIn * renderCrop->yScale) & ~1;

		return;
	}

	switch (adjust){
	case V_SCALE:
		*yOut = mathMain::FloatToInt(yIn * renderCrop->yScale) & ~1;
		*hOut = mathMain::FloatToInt(hIn * renderCrop->yScale) & ~1;

		break;
	case V_ALIGN_TOP:
		*yOut = mathMain::FloatToInt(yIn * renderCrop->aspectScale + renderCrop->aspectBias * (1.0f - percent)) & ~1;
		*hOut = mathMain::FloatToInt(hIn * renderCrop->aspectScale) & ~1;

		break;
	case V_ALIGN_CENTER:
		*yOut = mathMain::FloatToInt(yIn * renderCrop->aspectScale + renderCrop->aspectBias) & ~1;
		*hOut = mathMain::FloatToInt(hIn * renderCrop->aspectScale) & ~1;

		break;
	case V_ALIGN_BOTTOM:
		*yOut = mathMain::FloatToInt(yIn * renderCrop->aspectScale + renderCrop->aspectBias * (1.0f + percent)) & ~1;
		*hOut = mathMain::FloatToInt(hIn * renderCrop->aspectScale) & ~1;

		break;
	case V_STRETCH_TOP:
		*yOut = mathMain::FloatToInt(yIn * renderCrop->aspectScale + renderCrop->aspectBias * (1.0f - percent)) & ~1;
		*hOut = mathMain::FloatToInt(hIn * renderCrop->aspectScale + renderCrop->aspectBias * percent) & ~1;

		break;
	case V_STRETCH_HEIGHT:
		*yOut = mathMain::FloatToInt(yIn * renderCrop->aspectScale + renderCrop->aspectBias * (1.0f - percent)) & ~1;
		*hOut = mathMain::FloatToInt(hIn * renderCrop->aspectScale + renderCrop->aspectBias * percent * 2.0f) & ~1;

		break;
	case V_STRETCH_BOTTOM:
		*yOut = mathMain::FloatToInt(yIn * renderCrop->aspectScale + renderCrop->aspectBias) & ~1;
		*hOut = mathMain::FloatToInt(hIn * renderCrop->aspectScale + renderCrop->aspectBias * percent) & ~1;

		break;
	}
}


/*
 ==============================================================================

    SETUP VIEW

 ==============================================================================
*/


/*
 ==================
 R_SetupViewport
 ==================
*/
static void R_SetupViewport ()
{
	// Scale the coordinates and correct the aspect ratio if needed
	R_AdjustHorzCoordsInt(rg.renderView.horzAdjust, rg.renderView.horzPercent, rg.renderView.x, rg.renderView.width, &rg.renderViewParms.viewportX, &rg.renderViewParms.viewportWidth);
	R_AdjustVertCoordsInt(rg.renderView.vertAdjust, rg.renderView.vertPercent, rg.renderView.y, rg.renderView.height, &rg.renderViewParms.viewportY, &rg.renderViewParms.viewportHeight);

	// Clamp the viewport to the crop region
	rg.renderViewParms.viewportX = Max(rg.renderViewParms.viewportX, 0);
	rg.renderViewParms.viewportY = Max(rg.renderViewParms.viewportY, 0);
	rg.renderViewParms.viewportWidth = Min(rg.renderViewParms.viewportX + rg.renderViewParms.viewportWidth, rg.currentRenderCrop->width) - rg.renderViewParms.viewportX;
	rg.renderViewParms.viewportHeight = Min(rg.renderViewParms.viewportY + rg.renderViewParms.viewportHeight, rg.currentRenderCrop->height) - rg.renderViewParms.viewportY;
}

/*
 ==================
 R_SetupFOV
 ==================
*/
static void R_SetupFOV ()
{
	float	f;

	rg.renderViewParms.fovX = rg.renderView.fovX;
	rg.renderViewParms.fovY = rg.renderView.fovY;
	rg.renderViewParms.fovScale = rg.renderView.fovScale;

	// Correct the aspect ratio if needed
	if (rg.renderViewParms.subview != SUBVIEW_NONE)
		return;   // Ignore subviews

	if (rg.renderViewParms.viewportWidth * rg.renderView.height > rg.renderViewParms.viewportHeight * rg.renderView.width)
	{
		f = rg.renderViewParms.viewportHeight / mathMain::Tan(rg.renderViewParms.fovY / 360.0f * M_PI);
		rg.renderViewParms.fovX = mathMain::ATan(rg.renderViewParms.viewportWidth, f) * 360.0f / M_PI;
	}
	else if (rg.renderViewParms.viewportWidth * rg.renderView.height < rg.renderViewParms.viewportHeight * rg.renderView.width)
	{
		f = rg.renderViewParms.viewportWidth / mathMain::Tan(rg.renderViewParms.fovX / 360.0f * M_PI);
		rg.renderViewParms.fovY = mathMain::ATan(rg.renderViewParms.viewportHeight, f) * 360.0f / M_PI;
	}
}

/*
 ==================
 R_SetupFrustum
 ==================
*/
static void R_SetupFrustum ()
{
	int		i;

	if (r_zNear->floatValue < 0.1f)
		CVar_SetFloat(r_zNear, 3.0f);
	if (r_zFar->floatValue != 0.0f && r_zFar->floatValue <= r_zNear->floatValue)
		CVar_SetFloat(r_zFar, 0.0f);

	rg.renderViewParms.planeBits = BIT(FRUSTUM_LEFT) | BIT(FRUSTUM_RIGHT) | BIT(FRUSTUM_TOP) | BIT(FRUSTUM_BOTTOM) | BIT(FRUSTUM_NEAR);

	// Set up the left/right clip planes
	if (rg.renderViewParms.subview != SUBVIEW_MIRROR)
	{
		VectorCopy(rg.renderView.viewAxis[0], rg.renderViewParms.frustum[FRUSTUM_LEFT].normal);
		rg.renderViewParms.frustum[FRUSTUM_LEFT].dist = DotProduct(rg.renderView.viewOrigin, rg.renderViewParms.frustum[FRUSTUM_LEFT].normal) + r_zNear->floatValue;
	}
	else
	{
		VectorCopy(rg.renderSubviewParms.planeNormal, rg.renderViewParms.frustum[FRUSTUM_LEFT].normal);
		rg.renderViewParms.frustum[FRUSTUM_LEFT].dist = rg.renderSubviewParms.planeDist;
	}

	RotatePointAroundVector(rg.renderViewParms.frustum[FRUSTUM_RIGHT].normal, rg.renderView.viewAxis[2], rg.renderView.viewAxis[0], -(90 - rg.renderViewParms.fovX / 2));
	rg.renderViewParms.frustum[FRUSTUM_RIGHT].dist = DotProduct(rg.renderView.viewOrigin, rg.renderViewParms.frustum[FRUSTUM_RIGHT].normal);

	// Set up the top/bottom clip planes
	RotatePointAroundVector(rg.renderViewParms.frustum[FRUSTUM_TOP].normal, rg.renderView.viewAxis[2], rg.renderView.viewAxis[0], 90 - rg.renderViewParms.fovX / 2);
	rg.renderViewParms.frustum[FRUSTUM_TOP].dist = DotProduct(rg.renderView.viewOrigin, rg.renderViewParms.frustum[FRUSTUM_TOP].normal);

	RotatePointAroundVector(rg.renderViewParms.frustum[FRUSTUM_BOTTOM].normal, rg.renderView.viewAxis[1], rg.renderView.viewAxis[0], 90 - rg.renderViewParms.fovY / 2);
	rg.renderViewParms.frustum[FRUSTUM_BOTTOM].dist = DotProduct(rg.renderView.viewOrigin, rg.renderViewParms.frustum[FRUSTUM_BOTTOM].normal);

	// Set up the near clip plane
	RotatePointAroundVector(rg.renderViewParms.frustum[FRUSTUM_NEAR].normal, rg.renderView.viewAxis[1], rg.renderView.viewAxis[0], -(90 - rg.renderViewParms.fovY / 2));
	rg.renderViewParms.frustum[FRUSTUM_NEAR].dist = DotProduct(rg.renderView.viewOrigin, rg.renderViewParms.frustum[FRUSTUM_NEAR].normal);

	for (i = 0; i < NUM_FRUSTUM_PLANES; i++)
	{
		rg.renderViewParms.frustum[i].type = PLANE_NON_AXIAL;
		SetPlaneSignbits(&rg.renderViewParms.frustum[i]);
	}
}

/*
 ==================
 R_SetupMatrices
 ==================
*/
static void R_SetupMatrices ()
{
	mat4_t	transformMatrix, entityMatrix, mvpMatrix;
	vec4_t	clipPlane, corner;
	vec3_t	planeOrigin, planeNormal;
	float	xMin, yMin, xMax, yMax;
	float	xDiv, yDiv, zDiv;
	float	scale;

	// Compute perspective projection matrix
	xMax = r_zNear->floatValue * mathMain::Tan(rg.renderViewParms.fovX * M_PI / 360.0f);
	xMin = -xMax;

	yMax = r_zNear->floatValue * mathMain::Tan(rg.renderViewParms.fovY * M_PI / 360.0f);
	yMin = -yMax;

	xDiv = 1.0f / (xMax - xMin);
	yDiv = 1.0f / (yMax - yMin);
	zDiv = 1.0f / (r_zFar->floatValue - r_zNear->floatValue);

	rg.renderViewParms.perspectiveMatrix[ 0] = 2.0f * r_zNear->floatValue * xDiv;
	rg.renderViewParms.perspectiveMatrix[ 1] = 0.0f;
	rg.renderViewParms.perspectiveMatrix[ 2] = 0.0f;
	rg.renderViewParms.perspectiveMatrix[ 3] = 0.0f;
	rg.renderViewParms.perspectiveMatrix[ 4] = 0.0f;
	rg.renderViewParms.perspectiveMatrix[ 5] = 2.0f * r_zNear->floatValue * yDiv;
	rg.renderViewParms.perspectiveMatrix[ 6] = 0.0f;
	rg.renderViewParms.perspectiveMatrix[ 7] = 0.0f;
	rg.renderViewParms.perspectiveMatrix[ 8] = (xMax + xMin) * xDiv;
	rg.renderViewParms.perspectiveMatrix[ 9] = (yMax + yMin) * yDiv;
	rg.renderViewParms.perspectiveMatrix[10] = -0.9f;
	rg.renderViewParms.perspectiveMatrix[11] = -1.0f;
	rg.renderViewParms.perspectiveMatrix[12] = 0.0f;
	rg.renderViewParms.perspectiveMatrix[13] = 0.0f;
	rg.renderViewParms.perspectiveMatrix[14] = -2.0f * r_zNear->floatValue * 0.9f;
	rg.renderViewParms.perspectiveMatrix[15] = 0.0f;

	if (r_zFar->floatValue > r_zNear->floatValue)
	{
		rg.renderViewParms.perspectiveMatrix[10] = -(r_zNear->floatValue + r_zFar->floatValue) * zDiv;
		rg.renderViewParms.perspectiveMatrix[14] = -2.0f * r_zNear->floatValue * r_zFar->floatValue * zDiv;
	}

	// Compute world modelview matrix
	rg.renderViewParms.worldMatrix[ 0] = -rg.renderView.viewAxis[1][0];
	rg.renderViewParms.worldMatrix[ 1] = rg.renderView.viewAxis[2][0];
	rg.renderViewParms.worldMatrix[ 2] = -rg.renderView.viewAxis[0][0];
	rg.renderViewParms.worldMatrix[ 3] = 0.0f;
	rg.renderViewParms.worldMatrix[ 4] = -rg.renderView.viewAxis[1][1];
	rg.renderViewParms.worldMatrix[ 5] = rg.renderView.viewAxis[2][1];
	rg.renderViewParms.worldMatrix[ 6] = -rg.renderView.viewAxis[0][1];
	rg.renderViewParms.worldMatrix[ 7] = 0.0f;
	rg.renderViewParms.worldMatrix[ 8] = -rg.renderView.viewAxis[1][2];
	rg.renderViewParms.worldMatrix[ 9] = rg.renderView.viewAxis[2][2];
	rg.renderViewParms.worldMatrix[10] = -rg.renderView.viewAxis[0][2];
	rg.renderViewParms.worldMatrix[11] = 0.0f;
	rg.renderViewParms.worldMatrix[12] = DotProduct(rg.renderView.viewOrigin, rg.renderView.viewAxis[1]);
	rg.renderViewParms.worldMatrix[13] = -DotProduct(rg.renderView.viewOrigin, rg.renderView.viewAxis[2]);
	rg.renderViewParms.worldMatrix[14] = DotProduct(rg.renderView.viewOrigin, rg.renderView.viewAxis[0]);
	rg.renderViewParms.worldMatrix[15] = 1.0f;

	// Compute sky box texture matrix
	rg.renderViewParms.skyBoxMatrix[ 0] = 1.0f;
	rg.renderViewParms.skyBoxMatrix[ 1] = 0.0f;
	rg.renderViewParms.skyBoxMatrix[ 2] = 0.0f;
	rg.renderViewParms.skyBoxMatrix[ 3] = -rg.renderView.viewOrigin[0];
	rg.renderViewParms.skyBoxMatrix[ 4] = 0.0f;
	rg.renderViewParms.skyBoxMatrix[ 5] = 1.0f;
	rg.renderViewParms.skyBoxMatrix[ 6] = 0.0f;
	rg.renderViewParms.skyBoxMatrix[ 7] = -rg.renderView.viewOrigin[1];
	rg.renderViewParms.skyBoxMatrix[ 8] = 0.0f;
	rg.renderViewParms.skyBoxMatrix[ 9] = 0.0f;
	rg.renderViewParms.skyBoxMatrix[10] = 1.0f;
	rg.renderViewParms.skyBoxMatrix[11] = -rg.renderView.viewOrigin[2];
	rg.renderViewParms.skyBoxMatrix[12] = 0.0f;
	rg.renderViewParms.skyBoxMatrix[13] = 0.0f;
	rg.renderViewParms.skyBoxMatrix[14] = 0.0f;
	rg.renderViewParms.skyBoxMatrix[15] = 1.0f;

	if (rg.worldModel && rg.worldModel->sky->rotate)
		Matrix4_Rotate(rg.renderViewParms.skyBoxMatrix, rg.renderView.time * rg.worldModel->sky->rotate, rg.worldModel->sky->axis[0], rg.worldModel->sky->axis[1], rg.worldModel->sky->axis[2]);

	// Modify the perspective projection matrix if needed
	if (rg.renderViewParms.subview != SUBVIEW_MIRROR)
		return;

	// Transform the plane origin and normal to eye space
	Matrix4_TransformVector(rg.renderViewParms.worldMatrix, rg.renderSubviewParms.planeOrigin, planeOrigin);
	Matrix4_TransformNormal(rg.renderViewParms.worldMatrix, rg.renderSubviewParms.planeNormal, planeNormal);

	// Set the clipping plane
	VectorNormalize(planeNormal);

	clipPlane[0] = planeNormal[0];
	clipPlane[1] = planeNormal[1];
	clipPlane[2] = planeNormal[2];
	clipPlane[3] = -DotProduct(planeOrigin, planeNormal);

	// Calculate the clip space corner opposite the clipping plane and
	// transform it into eye space
	corner[0] = sgn(clipPlane[0] + rg.renderViewParms.perspectiveMatrix[ 8]) / rg.renderViewParms.perspectiveMatrix[ 0];
	corner[1] = sgn(clipPlane[1] + rg.renderViewParms.perspectiveMatrix[ 9]) / rg.renderViewParms.perspectiveMatrix[ 5];
	corner[2] = -1.0f;
	corner[3] = (1.0f + rg.renderViewParms.perspectiveMatrix[10]) / rg.renderViewParms.perspectiveMatrix[14];

	// Calculate the scaled clipping plane
	corner[0] *= clipPlane[0];
	corner[1] *= clipPlane[1];
	corner[2] *= clipPlane[2];
	corner[3] *= clipPlane[3];

	scale = 2.0f / (corner[0] + corner[1] + corner[2] + corner[3]);

	clipPlane[0] *= scale;
	clipPlane[1] *= scale;
	clipPlane[2] *= scale;
	clipPlane[3] *= scale;

	// Mirror the X axis and set the oblique near plane
	rg.renderViewParms.perspectiveMatrix[ 0] = -rg.renderViewParms.perspectiveMatrix[ 0];
	rg.renderViewParms.perspectiveMatrix[ 2] = clipPlane[0];
	rg.renderViewParms.perspectiveMatrix[ 6] = clipPlane[1];
	rg.renderViewParms.perspectiveMatrix[10] = clipPlane[2] + 1.0f;
	rg.renderViewParms.perspectiveMatrix[14] = clipPlane[3];

	// Compute mirror texture matrix if needed
	if (rg.renderSubviewParms.material->subviewWidth && rg.renderSubviewParms.material->subviewHeight)
	{
		if (rg.renderSubviewParms.entity == rg.worldEntity)
			Matrix4_Multiply(rg.renderViewParms.perspectiveMatrix, rg.renderViewParms.worldMatrix, mvpMatrix);
		else
		{
			Matrix4_Set(transformMatrix, rg.renderSubviewParms.entity->axis, rg.renderSubviewParms.entity->origin);
			Matrix4_MultiplyFast(rg.renderViewParms.worldMatrix, transformMatrix, entityMatrix);

			Matrix4_Multiply(rg.renderViewParms.perspectiveMatrix, entityMatrix, mvpMatrix);
		}

		Matrix4_Transpose(mvpMatrix, rg.renderViewParms.mirrorMatrix);
	}
}


/*
 ==============================================================================

    MAIN AND SUBVIEW RENDERING

 ==============================================================================
*/


// TODO!!!
static bool R_RenderSubview ()
{
	return true;
}

/*
 ==================
 R_RenderView
 ==================
*/
void R_RenderView ()
{
	// Bump view count
	rg.viewCount++;

	// Set up the viewport
	R_SetupViewport();

	if (rg.renderViewParms.viewportWidth < 1 || rg.renderViewParms.viewportHeight < 1)
		return;

	// Set up FOV, frustum, and matrices
	R_SetupFOV();
	R_SetupFrustum();
	R_SetupMatrices();

	// Add meshes and lights
	R_AddWorld();
	R_AddEntities();
	R_AddParticles();
	R_AddLights();

	// Sort meshes
	R_SortMeshes();

	// Set up renderViewParms
	rg.renderViewParms.meshes = &rg.viewData.meshes[rg.viewData.firstMesh];
	rg.renderViewParms.numMeshes = rg.viewData.numMeshes - rg.viewData.firstMesh;

	rg.renderViewParms.postProcessMeshes = &rg.viewData.postProcessMeshes[rg.viewData.firstPostProcessMesh];
	rg.renderViewParms.numPostProcessMeshes = rg.viewData.numPostProcessMeshes - rg.viewData.firstPostProcessMesh;

	rg.renderViewParms.lights = &rg.viewData.lights[rg.viewData.firstLight];
	rg.renderViewParms.numLights = rg.viewData.numLights - rg.viewData.firstLight;

	rg.renderViewParms.fogLights = &rg.viewData.fogLights[rg.viewData.firstFogLight];
	rg.renderViewParms.numFogLights = rg.viewData.numFogLights - rg.viewData.firstFogLight;

	// The next view rendered in this frame will tack on after this one
	rg.viewData.firstMesh += rg.renderViewParms.numMeshes;
	rg.viewData.firstPostProcessMesh += rg.renderViewParms.numPostProcessMeshes;

	rg.viewData.firstLight += rg.renderViewParms.numLights;
	rg.viewData.firstFogLight += rg.renderViewParms.numFogLights;

	// Render a subview if needed
	if (R_RenderSubview())
	{
		if (r_subviewOnly->integerValue)
			return;
	}

	// Add a render view command
	R_AddRenderViewCommand();

	// Add lights to the light editor if active
	R_AddEditorLights();
}